import serial
import csv
import datetime
import threading
import tkinter as tk
from tkinter import ttk, messagebox
import serial.tools.list_ports

class SerialMonitor:
    def __init__(self):
        self.physical_port = None
        self.virtual_port = None
        self.ser_physical = None
        self.ser_virtual = None
        self.monitoring = False
        self.log_file = None
        self.last_physical_data_time = None
        self.last_virtual_data_time = None

    def list_serial_ports(self):
        """列出系统中所有可用的串口"""
        ports = []
        for port in serial.tools.list_ports.comports():
            ports.append(port.device)
        return ports

    def open_ports(self):
        """打开物理端口和虚拟端口"""
        try:
            # 打开物理端口
            self.ser_physical = serial.Serial(
                port=self.physical_port,
                baudrate=115200,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1  # 减少超时以提高响应性
            )
            
            # 打开虚拟端口
            self.ser_virtual = serial.Serial(
                port=self.virtual_port,
                baudrate=115200,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1  # 减少超时以提高响应性
            )
            
            return True
        except Exception as e:
            messagebox.showerror("Error", f"Failed to open ports: {str(e)}")
            return False

    def close_ports(self):
        """关闭端口"""
        if self.ser_physical and self.ser_physical.is_open:
            self.ser_physical.close()
        if self.ser_virtual and self.ser_virtual.is_open:
            self.ser_virtual.close()

    def create_new_log_file(self):
        """创建新的日志文件"""
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        self.log_file = f"Serial_{timestamp}.csv"
        
        # 创建CSV文件并写入标题行
        try:
            with open(self.log_file, 'w', newline='', encoding='utf-8') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerow(['Timestamp', 'Direction', 'Data'])
        except Exception as e:
            print(f"Error creating log file: {e}")

    def log_data(self, direction, data):
        """记录数据到CSV文件"""
        # 精确到毫秒的时间戳
        now = datetime.datetime.now()
        timestamp = now.strftime('%Y-%m-%d %H:%M:%S') + '.' + str(now.microsecond // 1000).zfill(3)
        try:
            with open(self.log_file, 'a', newline='', encoding='utf-8') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerow([timestamp, direction, data])
        except Exception as e:
            print(f"Error writing to log file: {e}")

    def log_data_with_timestamp(self, timestamp, direction, data):
        """使用指定时间戳记录数据到CSV文件，数据以16进制格式写入"""
        try:
            # 将数据转换为16进制格式
            if isinstance(data, str):
                # 如果数据已经是字符串，先转换回字节
                hex_data = ' '.join([f'{ord(c):02X}' for c in data])
            else:
                # 如果数据是字节，直接转换为16进制
                hex_data = ' '.join([f'{b:02X}' for b in data])

            with open(self.log_file, 'a', newline='', encoding='utf-8') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerow([timestamp, direction, hex_data])
        except Exception as e:
            print(f"Error writing to log file: {e}")

    def forward_data(self):
        """转发数据的主循环，按帧解析并记录"""
        physical_buffer = bytearray()
        virtual_buffer = bytearray()
        FRAME_HEADER = b'\x24\x00'
        FRAME_SIZE = 7
        while self.monitoring:
            try:
                # 物理端口数据处理
                if self.ser_physical.in_waiting > 0:
                    if self.last_physical_data_time is None:
                        self.last_physical_data_time = datetime.datetime.now()
                    data = self.ser_physical.read(self.ser_physical.in_waiting)
                    if data:
                        self.ser_virtual.write(data)
                        physical_buffer.extend(data)
                        # 按帧解析
                        while len(physical_buffer) >= FRAME_SIZE:
                            if physical_buffer[0:2] == FRAME_HEADER:
                                frame = physical_buffer[:FRAME_SIZE]
                                now = self.last_physical_data_time
                                timestamp = now.strftime('%Y-%m-%d %H:%M:%S') + '.' + str(now.microsecond // 1000).zfill(3)
                                self.log_data_with_timestamp(timestamp, "Physical->Virtual", frame)
                                physical_buffer = physical_buffer[FRAME_SIZE:]
                                self.last_physical_data_time = None
                            else:
                                # 非帧头，丢弃第一个字节
                                physical_buffer = physical_buffer[1:]
                # 虚拟端口数据处理
                if self.ser_virtual.in_waiting > 0:
                    if self.last_virtual_data_time is None:
                        self.last_virtual_data_time = datetime.datetime.now()
                    data = self.ser_virtual.read(self.ser_virtual.in_waiting)
                    if data:
                        self.ser_physical.write(data)
                        virtual_buffer.extend(data)
                        # 按帧解析
                        while len(virtual_buffer) >= FRAME_SIZE:
                            if virtual_buffer[0:2] == FRAME_HEADER:
                                frame = virtual_buffer[:FRAME_SIZE]
                                now = self.last_virtual_data_time
                                timestamp = now.strftime('%Y-%m-%d %H:%M:%S') + '.' + str(now.microsecond // 1000).zfill(3)
                                self.log_data_with_timestamp(timestamp, "Virtual->Physical", frame)
                                virtual_buffer = virtual_buffer[FRAME_SIZE:]
                                self.last_virtual_data_time = None
                            else:
                                virtual_buffer = virtual_buffer[1:]
            except Exception as e:
                if self.monitoring:
                    messagebox.showerror("Error", f"Data forwarding error: {str(e)}")
                break

    def start_monitoring(self):
        """开始监控"""
        if not self.physical_port or not self.virtual_port:
            messagebox.showerror("Error", "Please select both physical and virtual ports.")
            return
            
        if self.open_ports():
            # 创建新的日志文件
            self.create_new_log_file()
            self.monitoring = True
            # 在新线程中运行数据转发
            self.thread = threading.Thread(target=self.forward_data, daemon=True)
            self.thread.start()
            return True
        return False

    def stop_monitoring(self):
        """停止监控"""
        self.monitoring = False
        self.close_ports()
        # 重置时间标记
        self.last_physical_data_time = None
        self.last_virtual_data_time = None

class SerialMonitorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Serial Port Monitor")
        self.root.geometry("400x300")
        
        self.monitor = SerialMonitor()
        
        self.create_widgets()
        self.update_port_list()

    def create_widgets(self):
        # 物理端口选择
        tk.Label(self.root, text="Physical Port:").grid(row=0, column=0, padx=10, pady=10, sticky="w")
        self.physical_port_var = tk.StringVar()
        self.physical_port_combo = ttk.Combobox(self.root, textvariable=self.physical_port_var, state="readonly")
        self.physical_port_combo.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
        
        # 虚拟端口选择
        tk.Label(self.root, text="Virtual Port:").grid(row=1, column=0, padx=10, pady=10, sticky="w")
        self.virtual_port_var = tk.StringVar()
        self.virtual_port_combo = ttk.Combobox(self.root, textvariable=self.virtual_port_var, state="readonly")
        self.virtual_port_combo.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
        
        # 刷新按钮
        self.refresh_button = tk.Button(self.root, text="Refresh Ports", command=self.update_port_list)
        self.refresh_button.grid(row=2, column=0, columnspan=2, pady=10)
        
        # 开始/停止按钮
        self.start_button = tk.Button(self.root, text="Start Monitoring", command=self.start_monitoring)
        self.start_button.grid(row=3, column=0, pady=10, sticky="e")
        
        self.stop_button = tk.Button(self.root, text="Stop Monitoring", command=self.stop_monitoring, state="disabled")
        self.stop_button.grid(row=3, column=1, pady=10, sticky="w")
        
        # 状态标签
        self.status_label = tk.Label(self.root, text="Status: Ready")
        self.status_label.grid(row=4, column=0, columnspan=2, pady=10)
        
        # 配置列权重
        self.root.grid_columnconfigure(1, weight=1)

    def update_port_list(self):
        """更新串口列表"""
        ports = self.monitor.list_serial_ports()
        self.physical_port_combo['values'] = ports
        self.virtual_port_combo['values'] = ports
        
        # 如果端口列表不为空，设置默认选择
        if ports:
            if not self.physical_port_var.get() and len(ports) > 0:
                self.physical_port_var.set(ports[0])
            if not self.virtual_port_var.get() and len(ports) > 1:
                self.virtual_port_var.set(ports[1])

    def start_monitoring(self):
        """开始监控按钮回调"""
        self.monitor.physical_port = self.physical_port_var.get()
        self.monitor.virtual_port = self.virtual_port_var.get()
        
        if self.monitor.start_monitoring():
            self.start_button.config(state="disabled")
            self.stop_button.config(state="normal")
            self.status_label.config(text=f"Status: Monitoring... Log file: {self.monitor.log_file}")
        else:
            self.status_label.config(text="Status: Error starting monitor")

    def stop_monitoring(self):
        """停止监控按钮回调"""
        self.monitor.stop_monitoring()
        self.start_button.config(state="normal")
        self.stop_button.config(state="disabled")
        self.status_label.config(text=f"Status: Stopped. Log saved to {self.monitor.log_file}")

def main():
    root = tk.Tk()
    app = SerialMonitorGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()